'''
GanZhiDate
必须输入m, dg, d,
不应该单独输入year。
year不是datetime的year，是干支对应的年。(2001年1月仍然属于庚辰年，year应该是2000，从立春开始才是辛巳年，2001)
year最好应该搭配年干yg，年支y，或者月干mg

一致性检查逻辑大致如下：
isYearExist为True，
valid_yg = (year-3)%10==yg
valid_y = (year-3)%12==y
valid_mg = (year-3)%5 == ((mg-m +20 - 12 if m<3 else mg-m +20)%10//2+1)%5

还有以下规则
1.过了23点算作第二天的日辰；
2.除了干支时间，我还希望获取下一个节气在什么时候（next_s,next_time），还希望知道，GanZhiDate对应的公历时间(current_time)；
3.repr打印出来GanZhiDate 的词典结构
'''
import os
import sqlite3
import datetime

__all__ = ['GanZhiDate', 'GAN', 'ZHI']

_start_year = 1900
_end_year = 2100

GAN = ["癸", "甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬"]  # 0-癸，1-甲，2-乙，3-丙，9-壬
ZHI = ["亥", "子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌"]  # 0-亥，1-子，2-丑，11-戌
KONG_WANG = ["戌亥", "子丑", "寅卯", "辰巳", "午未", "申酉"]


class GanZhiDate:
    def __init__(self, m, dg, d, hg=None, h=None, yg=None, y=None, mg=None, year=None, **kwargs):
        self.year = year  # 年干支对应的年份，与datetime.year不完全等同
        self.yg = yg  # 年干
        self.y = y  # 年支
        self.mg = mg  # month_gan，月干
        self.m = m  # month，月建
        self.dg = dg  # 日干，day_gan
        self.d = d  # day_zhi
        self.hg = hg  # 时干
        self.h = h  # hour，时辰

        # 将剩余的键值对设置为实例属性
        for key, value in kwargs.items():
            setattr(self, key, value)

        # 已知年份，月建，确定年干年支
        if self.year and self.m and not (self.yg and self.y):
            # if self.m == 2:
            #     self.yg, self.y = self.getYear(self.year - 1, int)
            # else:
            self.yg, self.y = self.getYear(self.year, int)

        # 初始化实例属性 mg,hg,time_info,empty
        if all(item is not None for item in [self.m, self.d, self.dg]):
            time_dict = self.toDict()
            result = self.getTimeInfo(time_dict)
            for key, value in result.items():
                setattr(self, key, value)
        else:
            raise NotImplementedError('未指定月日，无法根据干支日期确定日期')

    def __str__(self):
        try:
            return self.__getattribute__('time_info')
        except:
            raise ValueError('time_info 尚未计算')

    def __repr__(self):
        return str(self.toDict(isWhole=True))

    def toDict(self, isWhole=False) -> dict:
        if isWhole:
            return {key: value for key, value in self.__dict__.items() if value is not None}
        else:
            return {
                'year': self.year,
                'yg': self.yg,
                'y': self.y,
                'm': self.m,
                'dg': self.dg,
                'd': self.d,
                'h': self.h
            }

    @staticmethod
    def getTimeInfo(time_dict: dict) -> dict:
        '''
        这个函数补充了，
        int时间转汉字时间
        int时间计算空亡地支int
        :param time_dict: 至少包含 'm', 'dg', 'd' 三个键
        :return: {'time_info':str(如：甲辰年子月庚戌日午时空亡寅卯),'empty':int(空亡的第一个地支)}
        '''
        # 检查 m、dg 和 d 是否都存在且不为 None
        required_keys = ['m', 'dg', 'd']
        if not all(time_dict.get(key) is not None for key in required_keys):
            raise ValueError('日月不明')

        mg, hg = None, None

        if time_dict.get('yg') is not None:
            _1 = GAN[time_dict['yg'] % 10]
            month = time_dict['m'] if time_dict['m'] >= 3 else time_dict['m'] + 12
            # 甲己之年丙作首，乙庚之岁戊为头。丙辛之岁寻庚上，丁壬壬位顺行流。更有戊癸何处起，甲寅之上好追求。
            x0_lst = [-2, 0, 2, 4, 6]
            x0 = x0_lst[time_dict['yg'] % 5]
            mg = (month + x0) % 10  # 确定月干，month + gan
            _3 = GAN[mg]
        else:
            _1, _3 = '', ''

        _2 = _1 + ZHI[time_dict['y'] % 12] + '年' if time_dict.get('y') is not None else ''
        _4 = _3 + ZHI[time_dict['m'] % 12] + '月'
        _5 = GAN[time_dict['dg'] % 10] + ZHI[time_dict['d'] % 12] + '日'

        if time_dict.get('h') is not None:
            hour = time_dict['h'] if time_dict['h'] >= 1 else time_dict['h'] + 12
            # 甲己还加甲，乙庚丙作初，丙辛从戊起，丁壬庚子居，戊癸何方发？壬子是真途。
            x0_lst = [8, 0, 2, 4, 6]
            x0 = x0_lst[time_dict['dg'] % 5]
            hg = (hour + x0) % 10  # 确定时干，hour + gan
            _6 = GAN[hg] + ZHI[time_dict['h'] % 12] + '时'
        else:
            _6 = ''

        # 癸亥日，0-0，变成首位的“戌亥”
        ric_gan_gap = time_dict['d'] - time_dict['dg'] if time_dict.get('dg') else time_dict['d'] - 10
        empty = KONG_WANG[(ric_gan_gap + 12) % 12 // 2]

        # 如果hg，mg不为None，作为键值对加入result
        time_list = [_ for _ in [_2, _4, _5, _6] if _]
        result = {'time_info': ' '.join(time_list) + f'（空亡:{empty}）', 'empty': ZHI.index(empty[0])}
        if hg is not None:
            result['hg'] = hg
        if mg is not None:
            result['mg'] = mg

        return result

    @staticmethod
    def fromSolarDate(year, month, day, hour=12, changeDateWhenPast23=True):
        '''
        function
        阳历转干支
        特点：输入公历年月日
        usage GanZhiDate.fromSolarDate(2044,1,13)
        '''
        if year < _start_year or year > _end_year:
            raise ValueError(year, f'该年份不在节气数据表中，范围是 [{_start_year},{_end_year}]')

        solarDate = datetime.datetime(year, month, day, hour, 0)
        # 查询干支历法日期
        shi_chen = None if changeDateWhenPast23 else GanZhiDate.getHour(hour=hour)
        time_dict = GanZhiDate.convert(solarDate, mode=int, given_hour=shi_chen)  # 年干，年支，月建，月将，时辰
        return GanZhiDate(**time_dict)

    @staticmethod
    def fromDatetime(solarDatetime: datetime):
        '''
        function
        阳历转干支
        特点：传入datetime对象
        '''
        year = solarDatetime.year
        if year < _start_year or year > _end_year:
            raise ValueError(year, f'该年份不在节气数据表中，范围是 [{_start_year},{_end_year}]')

        # 查询干支历法日期
        time_dict = GanZhiDate.convert(solarDatetime, mode=int)  # 年干，年支，月建，月将，时辰
        return GanZhiDate(**time_dict)

    def toSolarDate(self, max_year=None) -> datetime:
        '''
        function
        干支转阳历
        process
        已知某年月日干支时间，求某年月日阳历时间
        调出某年某月节气时间 -> 节气时间转干支时间 -> 干支相减，计算天数差异 -> 某年某月节气时间 + 天数差异 = 某年某月阳历时间

        # 2000年对应于庚辰年，庚辰年丑月 GanZhiDate(2,3,9,yg=7,y=5) = GanZhiDate(2,3,9,year=2000) 实际在第二年的2001年
        # 如果你已知当前的datetime年份，
        # 切勿用datetime的 year 参与计算
        # 2001年阳历的一年之中，有两个子月乙丑日 datetime.date(2001, 1, 2)，datetime.date(2001, 12, 28)
        >>> GanZhiDate(3,3,9,yg=7,y=5).toSolarDate()
        datetime.date(2000, 2, 8)
        >>> GanZhiDate(1,0,2,year=2000).toSolarDate()
        datetime.date(2000, 12, 21)
        >>> GanZhiDate(1,2,2,year=2000).toSolarDate()
        datetime.date(2001, 1, 2)
        >>> GanZhiDate(1,2,2,yg=7,y=5).toSolarDate()
        datetime.date(2001, 1, 2)
        >>> GanZhiDate(1,6,10,yg=1,y=5).toSolarDate()
        datetime.date(2024, 12, 11)
        '''

        def get_seasonal_equinox_data(Year, m):
            assert isinstance(Year, int)
            assert 0 <= m <= 11

            if m == 2:  # 2024年甲辰年，若已经是丑月，则是干支历的小寒在阳历2025年1月
                Year += 1

            if _start_year <= Year <= _end_year:
                this_year: list[tuple] = get_data_by_year(year)
            else:
                raise ValueError(Year, f'该年份不在节气数据表中，范围是 [{_start_year},{_end_year}]')

            return Year, m - 1 if m > 1 else m + 11, this_year[2 * m - 4]

        # 没那么简单，年份都确定不好就免谈。
        # 1.需要年份，月建，干支日期。
        isYearExist = self.year is not None
        isGanZhiYearExist = self.yg is not None and self.y is not None
        if isYearExist:
            year = self.year
        else:
            if isGanZhiYearExist:
                # 找到已经过去的、最近的匹配的年份
                year = self.year = self.toYear(self.yg, self.y, max_year)
            else:
                raise NotImplementedError('未指定年份，无法根据干支日期确定日期')

        # 2.根据这一年的节气找同月的第一日。
        year, month, (day, hour, minute) = get_seasonal_equinox_data(year, self.m)

        # 3.转干支加减，计算天数差异
        season_dt = GanZhiDate.fromSolarDate(year, month, day, hour, False)  # 节气标准时间，转干支（同月的第一日）如果节气在23点会少算一天
        diff = self.calculate_diff(season_dt.dg, season_dt.d, self.dg, self.d)  # 未来干支 - 距离节气过去了多少天（月第一天干支）

        # 4.dt经过天数差异diff之后，到哪一天
        dt = datetime.date(year, month, day)  # 同月的第一日（datetime）
        dt += datetime.timedelta(days=diff)

        # 5.检验，如果计算出来的时间，不在本月，而是过了下一个节气点，到了下个月，代表这个月可能没有这个日辰。
        year, month, (day, _, _) = get_seasonal_equinox_data(year, (self.m + 1) % 12)
        if dt > datetime.date(year, month, day):
            raise ValueError(f'{year}年{ZHI[self.m]}月没有{GAN[self.dg]}{ZHI[self.d]}日')

        return dt

    @staticmethod
    def getYear(year, mode=str):
        '''
        这个year对应是和干支年份对应的年
        例如：2001年1，2月份有庚辰年，2月份立春之后是辛巳年，没有过立春的时候仍然算作庚辰年2000年
        >>> GanZhiDate.getYear(2000, int)
        (7, 5)
        >>> GanZhiDate.getYear(2000, str)
        '庚辰年'
        '''
        if mode == int:
            return (year - 3) % 10, (year - 3) % 12
        elif mode == str:
            gan = [GAN[-3 + i] for i in range(10)]
            zhi = [ZHI[-3 + i] for i in range(12)]
            return gan[year % 10] + zhi[year % 12] + '年'

    @staticmethod
    def toYear(yg: int, y: int, max_year=None) -> int:
        '''
        60年后再运行肯定会报错，因为更近的癸卯年是2083了
        >>> GanZhiDate.toYear(0, 4)
        2023
        '''

        # year + 60*k < now().year 求得最大整数k
        def find_max_k(year, maxYear=None):
            if not maxYear:
                maxYear = datetime.datetime.now().year
            difference = maxYear - year
            max_k = difference // 60
            return max_k

        # year - 3 同余 yg_remain （mod 10）；year - 3 同余 y_remain （mod 12）；求year的一个取值
        yg_remain = (yg + 3) % 10
        y_remain = (y + 3) % 12
        for k in range(5):
            year = y_remain + 12 * k
            if year % 10 == yg_remain:
                return year + find_max_k(year, max_year) * 60

    @staticmethod
    def convert(given_time: datetime, mode=str, given_hour: int = None, isMoreDetail=False) -> dict:
        '''
        - 返回的信息不全，缺少日月干
        给定datetime对象，转干支主要的逻辑。应当精确到分钟。
        mode = str | int | 'both'
        过了当天的23点，算作第二天的日辰.（ 你可以指定时辰 given_hour，绕过这道检测。）

        >>> GanZhiDate.convert(datetime.datetime(2024,12,21,21,4),int)
        {'year': 2024, 'yg': 1, 'y': 5, 'm': 1, 'mj': 2, 'dg': 6, 'd': 8, 'h': 0}
        >>> GanZhiDate.convert(datetime.datetime(2024,12,21,23,4),int)
        {'year': 2024, 'yg': 1, 'y': 5, 'm': 1, 'mj': 2, 'dg': 7, 'd': 9, 'h': 1}
        '''

        year = given_time.year
        this_year: list[tuple] = get_data_by_year(year)  # 选取这个年份的节气时间表，筛选掉已经过去很久的节气

        if len(this_year) < 24:
            raise ValueError('节气个数<24，说明这年份的节气数据不够!')

        time_string = ""
        i = given_time.month * 2 - 2
        while i < 24:
            month = i // 2 + 1
            day, hour, minute = this_year[i]
            dt = datetime.datetime(year, month, day, hour, minute)  # 节气时间点 dt，尚未到来
            if given_time < dt:
                time_string = f'{month}月{day}日{hour}时{minute}分'
                if given_time.month == month and given_time.day in [day - 1, day]:
                    time_string += '(【注】即将交节气)'

                year = year - 1 if i < 3 else year
                break
            i += 1
        else:
            i = 0
            if _start_year<=year + 1 <= _end_year:
                month = 1
                this_year: list[tuple] = get_data_by_year(year + 1)
                day, hour, minute = this_year[0]
                time_string = f'{month}月{day}日{hour}时{minute}分'

        h = GanZhiDate.getHour(hour=given_time.hour, given_hour=given_hour)

        dg, d = GanZhiDate.getDate(given_time, x_0=1 if given_time.hour == 23 and given_hour is None else 0)

        def time_detail():
            return {
                "next_time": seasons[i] + ' ' + time_string,
                "current_time": given_time.strftime('%Y-%m-%d %H:%M:%S'),
            }

        def format_str():
            return {
                "GanZhiYear": GanZhiDate.getYear(year, str),
                "M": MONTH_NAME[i - 1] if i % 2 else MONTH_NAME[i - 2],  # 0,2,4,6代表月建
                "Mj": MONTH_NAME[i - 2] if i % 2 else MONTH_NAME[i - 1],  # 1，3，5，7代表月将
                "D": GAN[dg] + ZHI[d] + '日',
                "H": ZHI[h % 12] + '时'
            }

        def format_int():
            return {
                "yg": (year - 3) % 10,
                "y": (year - 3) % 12,
                "m": ((i - 1) // 2 + 2) % 12,
                "mj": (14 - i // 2) % 12,
                "dg": dg,
                "d": d,
                "h": h % 12
            }

        if isMoreDetail:
            result = time_detail()
        else:
            result = {"year": year, }

        if mode == str:
            result.update(format_str())
        elif mode == int:
            result.update(format_int())
        elif mode == 'both':
            result.update(format_str())
            result.update(format_int())
        else:
            raise ValueError('mode参数不正确')

        return result

    @staticmethod
    def getDate(targetDate: datetime, x_0=0):
        targetDate = targetDate.date()  # 将 datetime 转换为 date
        startDate = datetime.date(2023, 7, 4)
        days_diff = (targetDate - startDate).days % 60 + 60 + x_0

        day_gan = days_diff % 10
        day_zhi = days_diff % 12

        return day_gan, day_zhi

    @staticmethod
    def getHour(hour: int = None, given_hour: int = None) -> int:
        # 阳历钟点，转时辰。given_hour是直接指定时辰了，不用算。
        if given_hour is not None and isinstance(given_hour, int):
            return given_hour % 12
        else:
            return 1 if hour == 23 else (hour + 1) // 2 + 1

    @staticmethod
    def calculate_diff(start_g, start_z, end_g, end_z) -> int:
        '''
        只要使用干支，也可以用于计算年月日时。
        例如给定起始日期，两个干支日，计算相差多少天。
        甲子日起，到己酉日，一共多少天？
        usage:
        >>> GanZhiDate.calculate_diff(1,1,6,10)
        45
        '''
        start_g = start_g if start_g % 10 else 10
        start_z = start_z if start_z % 12 else 12
        end_g = end_g if end_g % 10 else 10
        end_z = end_z if end_z % 12 else 12

        diff_g = (end_g - start_g + 10) % 10
        diff_z = (end_z - start_z + 12) % 12
        for k in range(5):
            diff = diff_z + 12 * k
            if diff % 10 == diff_g:
                return diff


def get_data_by_year(year: int) -> list[tuple]:
    if _start_year <= year <= _end_year:
        this_year: list[tuple] = query_by_year(year)
        return this_year
    else:
        raise ValueError('年份不在有效范围内')


def query_by_year(year):
    # 获取当前脚本的绝对路径
    script_dir = os.path.dirname(os.path.abspath(__file__))
    # 构建数据库文件的绝对路径
    db_path = os.path.join(script_dir, 'equinox_data.db')
    cursor = sqlite3.connect(db_path).cursor()
    cursor.execute("SELECT day,hour,minute FROM data WHERE year = ?", (year,))
    jieqi_data = cursor.fetchall()
    cursor.close()
    return jieqi_data


# seasonal_equinox_data = {}

seasons = (
    '小寒', '大寒', '立春', '雨水', '惊蛰', '春分', '清明', '谷雨', '立夏', '小满', '芒种', '夏至', '小暑', '大暑',
    '立秋', '处暑', '白露', '秋分', '寒露', '霜降', '立冬', '小雪', '大雪', '冬至',)
MONTH_NAME = (
    '丑月', '子将', '寅月', '亥将', '卯月', '戌将', '辰月', '酉将', '巳月', '申将', '午月', '未将', '未月', '午将',
    '申月', '巳将', '酉月', '辰将', '戌月', '卯将', '亥月', '寅将', '子月', '丑将',)

'''
> 获取月建，月将的数值
    for i in range(len(MONTH_NAME)):
        if i%2:
            print(i,13-i//2,MONTH_NAME[i], ZHI[(13-i//2)%12])
        else:
            print(i,i//2, MONTH_NAME[i], ZHI[(i//2+2)%12])
'''

if __name__ == '__main__':
    # ① 通过指定 年月日时干支，反推此时间 datetime 对象
    time_params = {
        'year': 2000,
        'm': 3,
        'dg': 3,
        'd': 9,
        'h': 0,
    }
    # gan_zhi_date = GanZhiDate(3, 3, 9, h=0, year=2000)
    gan_zhi_date = GanZhiDate(**time_params)
    print(gan_zhi_date)
    # 庚辰年 戊寅月 丙申日 己亥时（空亡:辰巳）
    print(gan_zhi_date.toSolarDate())
    # 2000-02-08
    print(gan_zhi_date.toDict(isWhole=True))
    # {'year': 2000, 'yg': 7, 'y': 5, 'mg': 5, 'm': 3, 'dg': 3, 'd': 9, 'hg': 6, 'h': 0, 'time_info': '庚辰年 戊寅月 丙申日 己亥时（空亡:辰巳）', 'empty': 5}

    # ② 指定年、月、日、时，计算年月日时干支（调用 fromSolar 方法）
    print(GanZhiDate.fromSolarDate(year=2025, month=2, day=4, hour=22))

    # ③ 指定 datetime 对象，计算年月日时干支（调用 fromSolarDatetime 方法）
    datetime_str = '1925-02-04 22:44:32'
    datetime_object = datetime.datetime.strptime(datetime_str, '%Y-%m-%d %H:%M:%S')
    # print(GanZhiDate.fromDatetime(datetime_object))
    # output:乙巳年 戊寅月 甲辰日 乙亥时（空亡:寅卯）
    print(GanZhiDate.convert(datetime_object, int, isMoreDetail=True))
    # output:{'next_time': '雨水 2月18日18时6分', 'current_time': '2025-02-04 22:44:32', 'yg': 2, 'y': 6, 'm': 3, 'mj': 1, 'dg': 1, 'd': 5, 'h': 0}
    print(GanZhiDate.convert(datetime_object, str, isMoreDetail=True))
    # output:{'next_time': '雨水 2月18日18时6分', 'current_time': '2025-02-04 22:44:32', 'GanZhiYear': '乙巳年', 'M': '寅月', 'Mj': '子将', 'D': '甲辰日', 'H': '亥时'}

    # 获取当前时间 datetime 对象
    now = datetime.datetime.now()
    print(GanZhiDate.fromDatetime(now))  # 打印干支时间
    print(GanZhiDate.convert(now, int, isMoreDetail=True))
